﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using iTunesLib;
using iTFF.WPF.Model.iTunes;
using System.ComponentModel;
using System.Runtime.InteropServices;
using iTFF.WPF.Resources.Languages;

namespace iTFF.WPF.CP
{
    class iTunesControlPanel : IDisposable
    {

        #region Constants

        /// <summary>
        /// The list of allowed actions when using the DoAction method
        /// </summary>
        static readonly string[] ALLOWED_ACTIONS = new string[] { "PlayPause", "NextTrack", "PreviousTrack", "Rewind", "FastForward" };

        #endregion

        #region Fields

        iTunesApp _application;
        bool _isiTunesBusy;

        #endregion

        #region Contructor

        public iTunesControlPanel()
        {
            _application = new iTunesApp();
            _application.OnPlayerPlayEvent += OnPlayerPlayEvent;
            _application.OnPlayerStopEvent += OnPlayerStopEvent;
            _application.OnAboutToPromptUserToQuitEvent += OnAboutToPromptUserToQuitEvent;
            _application.OnCOMCallsDisabledEvent += OnCOMCallsDisabledEvent;
            _application.OnCOMCallsEnabledEvent += OnCOMCallsEnabledEvent;
        }

        #endregion

        #region Properties from iTunes

        /// <summary>
        /// Gets the Current playing track, if iTunes is reachable
        /// </summary>
        public IITTrack PlayingTrack
        {
            get { return IsiTunesBusy ? null : _application.CurrentTrack; }
        }

        /// <summary>
        /// Gets the current Player State (playing, stopped, etc.), if iTunes is reachable
        /// </summary>
        public ITPlayerState? PlayerState
        {
            get { return IsiTunesBusy ? (ITPlayerState?)null : _application.PlayerState; }
        }

        /// <summary>
        /// Gets the current Player Position, if iTunes is reachable
        /// </summary>
        public int? PlayerPosition
        {
            get { return IsiTunesBusy ? (int?)null : _application.PlayerPosition; }
        }

        /// <summary>
        /// Gets or sets the song rate with stars, 0 to 5
        /// </summary>
        public int? SongRate
        {
            get { return IsiTunesBusy ? (int?)null : _application.CurrentTrack.Rating / 20; }
            set
            {
                if (!IsiTunesBusy)
                    _application.CurrentTrack.Rating = value.HasValue ? value.Value * 20 : 0;
                else
                    throw new ApplicationException(Strings.iTunesCPCannotSetSongRate);
            }
        }

        /// <summary>
        /// Gets or sets the volume, from 0 to 100
        /// </summary>
        public int? Volume
        {
            get { return IsiTunesBusy ? (int?)null : _application.SoundVolume; }
            set
            {
                if (!IsiTunesBusy)
                    _application.SoundVolume = value.HasValue ? value.Value : 0;
                else
                    throw new ApplicationException(Strings.iTunesCPCannotSetVolume);
            }
        }

        /// <summary>
        /// Gets or Sets the Mute toggle
        /// </summary>
        public bool? Mute
        {
            get { return IsiTunesBusy ? (bool?)null : _application.Mute; }
            set
            {
                if (!IsiTunesBusy)
                    _application.Mute = value.HasValue ? value.Value : false;
                else
                    throw new ApplicationException(Strings.iTunesCPCannotSetMute);
            }
        }

        /// <summary>
        /// Gets an array of Selected Tracks
        /// </summary>
        public IITTrack[] SelectedTracks
        {
            get
            {
                if (!IsiTunesBusy)
                {
                    if (_application.SelectedTracks == null)
                        return null;
                    else
                        return _application.SelectedTracks.Cast<IITTrack>().ToArray();
                }
                else
                    return null;
            }
        }

        /// <summary>
        /// Gets or Sets the Shuffle toggle for current playlist
        /// </summary>
        public bool? Shuffle
        {
            get
            {
                if (!IsiTunesBusy)
                {
                    if (_application.CurrentPlaylist != null)
                        return _application.CurrentPlaylist.Shuffle;
                }

                return null;
            }
            set
            {
                if (!IsiTunesBusy)
                {
                    if (_application.CurrentPlaylist != null)
                        _application.CurrentPlaylist.Shuffle = value.HasValue ? value.Value : false;
                    else
                        throw new ApplicationException(Strings.iTunesCPCannotSetShuffleBusy);
                }
                else
                    throw new ApplicationException(Strings.iTunesCPCannotSetShuffleBusy);
            }
        }

        #endregion

        #region Properties not form iTunes

        /// <summary>
        /// Indicates if iTunes is busy
        /// </summary>
        public bool IsiTunesBusy
        {
            get { return _isiTunesBusy; }
            set
            {
                if (_isiTunesBusy != value)
                {
                    _isiTunesBusy = value;
                    OnIsiTunesBusyChanged();
                }
            }
        }

        /// <summary>
        /// Gets the path of the current iTunes library path
        /// </summary>
        public string iTunesLibraryFolderPath
        {
            get
            {
                string[] parts = _application.LibraryXMLPath.Split('\\');
                return string.Join(@"\", parts, 0, parts.Length - 1);
            }
        }

        /// <summary>
        /// Gets an array of all playlists in the library
        /// </summary>
        public IITPlaylist[] Playlists
        {
            get
            {
                if (!IsiTunesBusy)
                {
                    return LibrarySource.Playlists.OfType<IITPlaylist>().ToArray();
                }
                else
                    return null;
            }
        }

        #endregion

        #region iTunes Events

        void OnPlayerPlayEvent(object iTrack)
        {
            OnPlayerPlayed((IITTrack)iTrack);
        }

        void OnPlayerStopEvent(object iTrack)
        {
            OnPlayerStopped((IITTrack)iTrack);
        }

        void OnAboutToPromptUserToQuitEvent()
        {
            // exit the application when itunes is about to be closed
            ControlPanel.Instance.SutdownApplication();
        }

        void OnCOMCallsEnabledEvent()
        {
            IsiTunesBusy = false;
        }

        void OnCOMCallsDisabledEvent(ITCOMDisabledReason reason)
        {
            IsiTunesBusy = true;
        }

        #endregion

        #region Own events

        /// <summary>
        /// Event thrown when iTunes becomes busy or available
        /// </summary>
        public event EventHandler<IsiTunesBusyChangedEventArgs> IsiTunesBusyChanged;

        protected void OnIsiTunesBusyChanged()
        {
            if (IsiTunesBusyChanged != null)
                IsiTunesBusyChanged(this, new IsiTunesBusyChangedEventArgs(IsiTunesBusy));
        }

        /// <summary>
        /// Event thrown when player begins to play
        /// </summary>
        public event EventHandler<iTunesTrackEventArgs> PlayerPlayed;

        protected void OnPlayerPlayed(IITTrack track)
        {
            if (PlayerPlayed != null)
                PlayerPlayed(this, new iTunesTrackEventArgs(track));
        }

        /// <summary>
        /// Event thrown when player stops to play
        /// </summary>
        public event EventHandler<iTunesTrackEventArgs> PlayerStopped;

        protected void OnPlayerStopped(IITTrack track)
        {
            if (PlayerStopped != null)
                PlayerStopped(this, new iTunesTrackEventArgs(track));
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Toggles Shuffle for current playlist.
        /// </summary>
        public void ToggleShuffle()
        {
            Shuffle = !Shuffle;
        }

        /// <summary>
        /// Toggles Mute
        /// </summary>
        public void ToggleMute()
        {
            Mute = !Mute;
        }

        /// <summary>
        /// Executes the methods on the underlying iTunes object, using Reflection.
        /// </summary>
        /// <param name="action"></param>
        public void DoAction(string action)
        {
            if (!ALLOWED_ACTIONS.Contains(action))
                throw new ApplicationException(Strings.iTunesCPActionNotAllowed);

            if (IsiTunesBusy)
                throw new ApplicationException(Strings.iTunesCPiTunesBusy);

            if (_application != null)
            {
                Type t = _application.GetType();

                var method = t.GetMethod(action);

                if (method != null)
                    method.Invoke(_application, null);
                else
                    throw new ApplicationException(Strings.iTunesCPCannotExecuteAction);

            }
            else
                throw new Exception("Can't excecute action while application is null");
        }

        /// <summary>
        /// Returns all tracks in the library where artist and album are the same as the specified track
        /// </summary>
        /// <param name="track"></param>
        /// <returns>The collection or null if iTunes is busy</returns>
        public IITTrack[] GetAllTracksInSameAlbum(IITTrack track)
        {
            if (!IsiTunesBusy)
                return _application.LibraryPlaylist.Tracks.Cast<IITTrack>()
                    .Where(t => t.Album == track.Album && t.Artist == track.Artist).ToArray();
            else
                return null;
        }

        /// <summary>
        /// Creates a playlist with the specified name or returns the existing one. 
        /// May throw COMException if there are many playlists in the library and if the user makes itunes busy.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public IITUserPlaylist CreateOrGetPlaylist(string name)
        {
            if (!IsiTunesBusy)
            {
                var pl = LibrarySource.Playlists.OfType<IITUserPlaylist>().FirstOrDefault(p => p.Name == name);
                if (pl == null)
                    pl = (IITUserPlaylist)_application.CreatePlaylist(name);

                return pl;
            }
            else
                return null;
        }

        /// <summary>
        /// Clears a playlist from all its tracks
        /// </summary>
        /// <param name="pl"></param>
        public bool ClearPlaylist(IITPlaylist pl)
        {
            if (!IsiTunesBusy)
            {
                int c = pl.Tracks.Count;
                for (int i = 0; i < c; i++)
                    pl.Tracks[1].Delete();
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// Gets a track its IDs
        /// </summary>
        /// <param name="s"></param>
        /// <param name="p"></param>
        /// <param name="t"></param>
        /// <param name="d"></param>
        /// <returns>The track or null if not found or if iTunes is busy</returns>
        public IITTrack GetTrackFromTrackDatabaseID(int s, int p, int t, int d)
        {
            if (!IsiTunesBusy)
                return (IITTrack)_application.GetITObjectByID(s, p, t, d);
            else
                return null;
        }

        /// <summary>
        /// Gets the PersistentID (used in xml store) for a specified track or playlist
        /// </summary>
        /// <param name="obj">An IITObject</param>
        /// <returns></returns>
        public UInt64 GetObjectPersistentID(object obj)
        {
            if (IsiTunesBusy)
                return 0;

            int highID, lowID = 0;
            
            _application.GetITObjectPersistentIDs(ref obj, out highID, out lowID);

            return (((ulong)highID << 32) & 0xFFFFFFFF00000000 | ((ulong)lowID & 0x00000000FFFFFFFF));
        }

        /// <summary>
        /// Gets the track from its id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public IITTrack GetTrackFromPersistentID(ulong id)
        {
            if (IsiTunesBusy)
                return null;

            int highID, lowID = 0;

            lowID = (int) id >> 32;
            highID = (int)(id >> 32);

            var obj =  _application.LibraryPlaylist.Tracks.get_ItemByPersistentID(highID, lowID);
            return obj as IITTrack;
        }

        /// <summary>
        /// Gets an hex representation of the Persistent ID
        /// </summary>
        /// <param name="persistentID"></param>
        /// <returns></returns>
        public string GetHexRepresentationFromPersistentID(ulong persistentID)
        {
            return persistentID.ToString("X");
        }

        /// <summary>
        /// Gets the ulong value from the HEX string
        /// </summary>
        /// <param name="hex"></param>
        /// <returns></returns>
        public ulong GetUInt64FromPersistentIDHex(string hex)
        {
            return ulong.Parse(hex, System.Globalization.NumberStyles.HexNumber);
        }

        /// <summary>
        /// Gets the first file of the library playlist
        /// Used in tests
        /// </summary>
        /// <returns></returns>
        public IITTrack GetFirstLibraryTrack()
        {
            if (IsiTunesBusy)
                return null;

            return _application.LibraryPlaylist.Tracks.get_ItemByPlayOrder(1);
        }

        /// <summary>
        /// Adds the playing song to a playlist which name is the same as the artist 
        /// </summary>
        /// <returns></returns>
        public void AddPlayingTrackToArtistNamePlaylist()
        {
            if (PlayingTrack != null)
            {
                var pl = CreateOrGetPlaylist(PlayingTrack.Artist);
                var obj = (object)PlayingTrack;
                pl.AddTrack(ref obj);

                // show the toast just as a confirmation
                ControlPanel.Instance.ToasterControlPanel.ShowToast();
            }
        }

        #endregion

        #region Artwork

        /// <summary>
        /// Sets Artwork into id3 tags for a track collection
        /// </summary>
        /// <param name="worker">A BackgroundWorker to report progess</param>
        /// <param name="tracks">The track collection</param>
        /// <param name="filePath">The image file path</param>
        public void SetArtwork(BackgroundWorker worker, IITTrack[] tracks, string filePath)
        {
            int i = 0;
            worker.ReportProgress(i, "SetArtwork");
            foreach (var t in tracks)
            {
                try
                {
                    SetArtwork(t, filePath);
                    worker.ReportProgress(++i * 100 / tracks.Length, "SetArtwork");

                    // break if cancellation pending
                    if (worker.CancellationPending)
                        return;
                }
                catch (COMException)
                {
                    throw new ApplicationException(Strings.iTunesBecameBusy);
                }
            }
        }

        /// <summary>
        /// Sets Artwork for a track
        /// </summary>
        /// <param name="t">The track</param>
        /// <param name="filePath">The image file path</param>
        private void SetArtwork(IITTrack t, string filePath)
        {
            // vider les artworks actuels
            while (t.Artwork.Count != 0)
                t.Artwork[1].Delete();

            // appliquer l'actuel
            t.AddArtworkFromFile(filePath);
        }

        #endregion

        #region Lyrics

        /// <summary>
        /// Returns all the songs without lyrics. May raise COMException if iTunes becomes busy in the process
        /// </summary>
        /// <param name="lyricsExists"></param>
        /// <returns></returns>
        public IITFileOrCDTrack[] GetSongsWithoutLyrics(Predicate<IITFileOrCDTrack> lyricsExists)
        {
            if (!IsiTunesBusy)
            {
                return _application.LibraryPlaylist.Tracks.Cast<IITTrack>()
                        .OfType<IITFileOrCDTrack>()
                        .Where(t => lyricsExists(t))
                        .ToArray();
            }
            else
                return null;
        }

        /// <summary>
        /// Delete the lyrics from all tracks. FOR DEBUG PURPOSE ONLY
        /// </summary>
        /// <param name="l"></param>
        public void DeleteAllLyrics(Action<IITFileOrCDTrack> deleteAction)
        {
            foreach (var t in _application.LibraryPlaylist.Tracks.OfType<IITFileOrCDTrack>())
                deleteAction.Invoke(t);
        }

        #endregion

        #region Private helpers

        /// <summary>
        /// Gets the iTunes Library as IITSource
        /// </summary>
        private IITSource LibrarySource
        {
            get
            {
                if (!IsiTunesBusy)
                    return _application.Sources.Cast<IITSource>().FirstOrDefault(s => s.Kind == ITSourceKind.ITSourceKindLibrary);
                else
                    return null;
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (!IsiTunesBusy)
            {

                _application.OnPlayerPlayEvent -= OnPlayerPlayEvent;
                _application.OnPlayerStopEvent -= OnPlayerStopEvent;
                _application.OnAboutToPromptUserToQuitEvent -= OnAboutToPromptUserToQuitEvent;
                _application.OnCOMCallsDisabledEvent -= OnCOMCallsDisabledEvent;
                _application.OnCOMCallsEnabledEvent -= OnCOMCallsEnabledEvent;

                // release com object to notify iTunes that iTFF isn't still running
                Marshal.ReleaseComObject(_application);
                _application = null;
            }
        }

        #endregion
    }
}
